package comment

import (
	"HeadLineNews/controller/manage/news"
	"HeadLineNews/pkg/db/store"
	"HeadLineNews/pkg/snow"
	"HeadLineNews/pkg/zaplog"
	models "HeadLineNews/proto_models/app/comment_model"
	"HeadLineNews/resp"
	"HeadLineNews/utils/gin_ctx"
	"HeadLineNews/utils/transaction"
	"database/sql"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/jmoiron/sqlx"
	"strings"
)

const (
	floorCommentNum = 5 // 每层楼显示评论数量
)

var (
	errReplyCommentNotExits  = errors.New("被回复的评论不存在")
	errFloorCommentNotExists = errors.New("该楼层评论不存在")
)

type Repo interface {
	CheckNewExists(c *gin.Context, newsId string) (err error)
	CheckReplyIdExists(c *gin.Context, replyId string) (floorId string, err error)
	InsertComment(c *gin.Context, p *models.RequestUserComment, floorId string) (err error)

	CheckNewsExists(c *gin.Context, newsId string) (err error)
	RetrieveNewsComments(c *gin.Context, p *models.RequestRetrieveNewsComment) (data *models.ResponseRetrieveNewsComment, err error)

	CheckFloorCommentExists(c *gin.Context, newsId, floorId string) (err error)
	RetrieveNewsFloorComments(c *gin.Context, p *models.RequestRetrieveNewsFloorComment) (data *models.ResponseRetrieveNewsFloorComment, err error)
}

type repo struct {
	db *sqlx.DB
}

func NewRepo() Repo {
	return &repo{db: store.DB()}
}

// CheckNewExists 检查提交的被评论的新闻id是否存在
func (r *repo) CheckNewExists(c *gin.Context, newsId string) (err error) {
	_, err = news.NewRepo().CheckNewsExists(c, newsId)
	return
}

// CheckReplyIdExists replyId 不为空时，查询 floorId
func (r *repo) CheckReplyIdExists(c *gin.Context, replyId string) (floorId string, err error) {
	sqlStr := `SELECT floor_id FROM app__news_comment WHERE comment_id=?`
	if err = r.db.Get(&floorId, sqlStr, replyId); err != nil {
		if err == sql.ErrNoRows {
			err = errReplyCommentNotExits
			zaplog.L().Warn("invalid replyId", zaplog.Fields(c, replyId, nil)...)
			resp.UnprovedParam(c, err.Error())
			return
		}
		zaplog.L().Error("query reply comment by replyId failed", zaplog.Fields(c, replyId, err)...)
		resp.ServerBusy(c)
		return
	}
	return
}

// InsertComment 将评论保存进数据库
func (r *repo) InsertComment(c *gin.Context, p *models.RequestUserComment, floorId string) (err error) {
	var sqlStr string
	var args []interface{}
	if p.ReplyId == "" { // replyId 为空的就不保存
		sqlStr = `INSERT INTO app__news_comment(
                comment_id, news_id, floor_id, content, create_by, create_time) VALUES (
                    ?,?,?,?,?,NOW())`
		args = []interface{}{snow.GetID(), p.NewsId, snow.GetID(), p.Comment, c.GetString(gin_ctx.UserId)}
	} else { // replyId 不为空的时候
		sqlStr = `INSERT INTO app__news_comment(
                comment_id, news_id, floor_id, reply_id,content, create_by, create_time) VALUES (
                    ?,?,?,?,?,?,NOW())`
		args = []interface{}{snow.GetID(), p.NewsId, floorId, p.ReplyId, p.Comment, c.GetString(gin_ctx.UserId)}
	}
	if _, err = r.db.Exec(sqlStr, args...); err != nil {
		zaplog.L().Error("insert comment failed", zaplog.Fields(c, p, err)...)
		resp.ServerBusy(c)
		return
	}
	return
}

// CheckNewsExists 检查新闻是否存在
func (r *repo) CheckNewsExists(c *gin.Context, newsId string) (err error) {
	_, err = news.NewRepo().CheckNewsExists(c, newsId)
	return
}

// RetrieveNewsComments 检索某一条新闻的评论
func (r *repo) RetrieveNewsComments(c *gin.Context, p *models.RequestRetrieveNewsComment) (data *models.ResponseRetrieveNewsComment, err error) {
	data = new(models.ResponseRetrieveNewsComment)
	if err = transaction.Transaction(r.db, func(tx *sqlx.Tx) (te error) {
		sqlStr := `SELECT COUNT(*) FROM app__news_comment WHERE news_id=?`
		// 1.查看新闻多少评论
		var count int64
		if te = tx.Get(&count, sqlStr, p.NewsId); te != nil {
			zaplog.L().Error("query news comment count by newsId failed", zaplog.Fields(c, p.NewsId, err)...)
			resp.ServerBusy(c)
			return
		}
		if count == 0 { // 没有评论直接结束函数
			return
		}
		// 2.按照楼层分组，每层楼取前五条评论（分组取前五条）
		allComments := make([]*models.AComment, 0, p.PageSize.Size)
		sqlStr = `SET @r:=1;`
		_, _ = tx.Exec(sqlStr)
		sqlStr = `SET @g:='';`
		_, _ = tx.Exec(sqlStr)
		sqlStr = `SELECT a.comment_id,IFNULL(a.reply_id,'') AS reply_id ,
       			a.content,a.create_time,a.floor_id,a.row_num,
       			ac.user_id, ac.user_name FROM (
				      SELECT *, IF(@g=floor_id, @r:=@r+1, @r:=1) AS row_num, @g:=floor_id   
			          FROM app__news_comment
			          ORDER BY floor_id, create_time) a,
			 app__account ac
			 WHERE a.create_by=ac.user_id 
			 AND a.news_id=? 
			 AND a.row_num<=?
			 ORDER BY a.floor_id, a.create_time LIMIT ? OFFSET ?`
		if te = tx.Select(&allComments, sqlStr,
			p.NewsId, floorCommentNum, p.PageSize.Size, (p.PageSize.Page-1)*p.PageSize.Size); te != nil {
			zaplog.L().Error("query allComments failed", zaplog.Fields(c, p.NewsId, te)...)
			resp.ServerBusy(c)
			return
		}

		// 将里面所有的楼层的 楼层id 取出，查询这个楼层的评论数量
		var floorIds []string // 为楼层评论的评论id
		for _, v := range allComments {
			if v.ReplyId == "" {
				floorIds = append(floorIds, v.FloorId)
			}
		}
		sqlStr = `SELECT COUNT(*) AS floor_comment_count, floor_id FROM app__news_comment
				WHERE FIND_IN_SET(floor_id, ?) GROUP BY floor_id`
		type aFloorCommentCount struct {
			FloorId           string `db:"floor_id"`
			FloorCommentCount int64  `db:"floor_comment_count"`
		}
		floorCommentCountSl := make([]*aFloorCommentCount, 0, len(floorIds))
		if te = tx.Select(&floorCommentCountSl, sqlStr, strings.Join(floorIds, ",")); te != nil {
			zaplog.L().Error("query floor comment count failed", zaplog.Fields(c, floorIds, te)...)
			return
		}

		// 数据组装
		data.TotalCommentCount = count // 评论总数

		var aFloorComment = new(models.AFloorComment) // 某一层楼的评论 + 评论数量
		floorId := allComments[0].FloorId
		for i, v := range allComments {
			// 相等的 floor_id append
			if v.FloorId == floorId {
				aFloorComment.Comments = append(aFloorComment.Comments, v)

				// 追加最后的数据
				if i == len(allComments)-1 {
					for _, vv := range floorCommentCountSl {
						if floorId == vv.FloorId {
							aFloorComment.FloorCommentCount = vv.FloorCommentCount
						}
					}
					data.FloorComments = append(data.FloorComments, aFloorComment)
				}
			} else {
				// 根据 floor_id 找对应的 评论数量
				for _, vv := range floorCommentCountSl {
					if floorId == vv.FloorId {
						aFloorComment.FloorCommentCount = vv.FloorCommentCount
					}
				}
				// 每一层楼的评论（可显示评论数量+评论总数）
				data.FloorComments = append(data.FloorComments, aFloorComment)
				// 刷新数据再循环
				aFloorComment = new(models.AFloorComment)
				floorId = v.FloorId
				aFloorComment.Comments = append(aFloorComment.Comments, v) // 添加循环当前层评论
			}
		}
		return
	}); err != nil {
		resp.ServerBusy(c)
		return
	}

	return
}

// CheckFloorCommentExists 检查楼层是否存在
func (r *repo) CheckFloorCommentExists(c *gin.Context, newsId, floorId string) (err error) {
	sqlStr := `SELECT COUNT(*) FROM app__news_comment WHERE news_id=? AND floor_id=?`
	var count int64
	if err = r.db.Get(&count, sqlStr, newsId, floorId); err != nil {
		zaplog.L().Error("query comment count failed",
			zaplog.Fields(c, fmt.Sprintf("newsId:%s, fllorId:%s", newsId, floorId), err)...)
		resp.ServerBusy(c)
		return
	}
	if count == 0 {
		err = errFloorCommentNotExists
		zaplog.L().Warn("invalid newsId or floorId",
			zaplog.Fields(c, fmt.Sprintf("newsId:%s, fllorId:%s", newsId, floorId), nil)...)
		resp.UnprovedParam(c, err.Error())
		return
	}
	return
}

// RetrieveNewsFloorComments 检索新闻某一层楼的评论
func (r *repo) RetrieveNewsFloorComments(c *gin.Context, p *models.RequestRetrieveNewsFloorComment) (data *models.ResponseRetrieveNewsFloorComment, err error) {
	sqlStr := `SELECT ac.user_id, ac.user_name, c.comment_id, c.content, c.create_time, 
       IFNULL(c.reply_id, '') AS reply_id
		FROM app__news_comment c, app__account ac
		WHERE c.create_by=ac.user_id
		AND c.news_id=?
		AND c.floor_id=?
		LIMIT ? OFFSET ?`
	data = new(models.ResponseRetrieveNewsFloorComment)
	comments := make([]*models.AComment, 0, p.PageSize.Size)
	if err = r.db.Select(&comments, sqlStr, p.NewsId, p.FloorId,
		p.PageSize.Size, (p.PageSize.Page-1)*p.PageSize.Size); err != nil {
		zaplog.L().Error("query floor comments failed", zaplog.Fields(c, p, err)...)
		resp.ServerBusy(c)
		return
	}
	if len(comments) == 0 {
		return
	}
	var count int64
	sqlStr = `SELECT COUNT(*) FROM app__news_comment WHERE news_id=? AND floor_id=?`
	if err = r.db.Get(&count, sqlStr, p.NewsId, p.FloorId); err != nil {
		zaplog.L().Error("query floor comments count failed",
			zaplog.Fields(c, fmt.Sprintf("newsId:%s, fllorId:%s", p.NewsId, p.FloorId), err)...)
		resp.ServerBusy(c)
		return
	}
	data.Comments = comments
	data.CommentCount = count
	return
}
